package hms.database;

import hms.master.MasterController;

import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Iterator;

/**
 * The DBRowList<E> extends an ArrayList<E>, so it can be used in much the same manner.
 * It adds methods to commit changes to the entire list to the database and to load up
 * an entire table into the list.  E is the class that represents your DB table's rows
 * in Java.  E must be a subclass of DBRowObject.
 * 
 * @author Ben
 *
 * @param <E>
 */
public abstract class DBRowList<E extends DBRowObject> extends ArrayList<E> {

	/**
	 * Auto-generated code
	 */
	private static final long serialVersionUID = -188956942434833597L;
	
	/**
	 * Commit changes to database (where changed items were marked with
	 * markAsChanged())
	 * <p>
	 * When committing, objects marked for deletion from the database
	 * will be removed.  
	 */
	public void commitToDatabase() {
		Iterator<E> i = this.iterator();
		while(i.hasNext()) {
			E obj = i.next();
			obj.commitToDatabase();
			if(obj.isMarkedForDeletion()) {
				i.remove();
			}
		}
	}

	/**
	 * Due to a limitation of genetics, DBRowList cannot create a new instance
	 * of the type of object being contained without explicitly being told
	 * what the class in.  What a pain!
	 * <p>
	 * To resolve this issue, this function must call one line of code, which
	 * the subclass must implement:
	 * <p>
	 * fillFromDatabase(YourClassNameHere.class);
	 * <p>
	 * For instance, if you are filling out TerminationReason objects, then the
	 * call would look like the following example code.
	 * <p>
	 * <i>Example code:</i>
	 * <ul>
	 * <li>fillFromDatabase(TerminationReason.class);
	 * </ul>
	 */
	public abstract void fillFromDatabase();
	
	/**
	 * Reloads the list from the database.  All changes to the current list are discarded.
	 */
	public void reloadFromDatabase() {
		this.clear();
		fillFromDatabase();
	}
	
	
	/**
	 * This method queries the database once for information.  It then creates
	 * an instance of the class for each row, and passes the ResultSet to the
	 * new object with the current row set to the data for the new object.
	 * 
	 * @param elementType Class literal of the objects this list contains.
	 */
	protected void fillFromDatabase(Class<E> elementType) {
		E newObject = null;
		try {
			newObject = elementType.newInstance();
			
			// Build checking string
			String checkIDString = "SELECT * FROM " + newObject.getTableName() + ";";
			
			// Query database
			DAO localDAO = getDAO();
			ResultSet rs = localDAO.getResult(checkIDString);
			closeDAO();
			
			while(rs.next()) {
				E newElement = elementType.newInstance();
				newElement.setUniqueIdentifier(rs.getInt(newElement.getUniqueIdentifierFieldName()));
				newElement.loadFromDatabase(rs);
				this.add(newElement);
				newElement.markAsSynchedWithDatabase();
			}

		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception ex) {
			System.out.println("Error while filling from database");
			ex.printStackTrace();
		}
	}
	
	/**
	 * For when this code is NOT run through the main code, create a connection
	 * to the DB and open it, otherwise get the global connection.
	 * <p>
	 * BE SURE to close the DAO at the end of your task code!
	 * 
	 * @return DAO to database.
	 * @see #closeDAO
	 */
	private DAO getDAO() {
		return DAOnoGUI.getDAO();
	}
	
	/**
	 * Closes DAO if and only if it was locally opened.  MUST call of getDAO() was called.
	 * 
	 * @see #getDAO
	 */
	private void closeDAO() {
		DAOnoGUI.closeDAO();
	}
	
	
	/**
	 * Returns an ArrayList of all the contained objects not marked for deletion.
	 * 
	 * @return
	 */
	public ArrayList<E> notQueuedForDeletion() {
		
		ArrayList<E> out =  new ArrayList<E>();
		for(E element : this) {
			if(!element.isMarkedForDeletion()) {
				out.add(element);
			}
		}
		return out;
	}
}
